package modeling;


/**
 * @author Mathieu Rousseau
 * @email mathieu.rousseau3@mail.mcgill.ca
 */


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;


/** ------------------------------------------------------------------------------------------------------------------
 * start of NormalizationGeneDesert class
 * ------------------------------------------------------------------------------------------------------------------  **/
public class NormalizationGeneDesert {


	/*  global variables  */
	private String inputPath = "NormalizationGeneDesert/Input/";
	private String outputPath = "NormalizationGeneDesert/Output/";
	private NumberFormat formatter = new DecimalFormat("#0.0000");
	private double normalizationFactor = 0;
	/*  end of global variables  */

	
	/*  start of getValuesFromFile method  */
	private void getValuesFromFile(String filename, HashMap<String, double[]> valueHashMap){

		//  open and read the data file
		
		String line = null;
		
		try{
			BufferedReader br = new BufferedReader(new FileReader(inputPath + filename));			
			
			while((line = br.readLine()) != null){
			
				String[] values = line.split("\t");
				
				if(values.length != 10){
					System.out.println("Error: there is an entry in \"" + inputPath + filename + "\" that does not have ten (10) values" +
							" (i.e. index1, index2, feature 1, feature 2, ..., feature 8)\nSystem.exit(0)");
					System.exit(0);
				}
				
				String keyValue = values[0] + "-" + values[1];			//  this should create unique key values
//				System.out.print(keyValue + "\t");
				
				double[] rowEntry = new double[10];
				
				for(int i=0; i<10; i++){
					rowEntry[i] = Double.parseDouble(values[i]);
//					System.out.print(rowEntry[i] + "\t");
				}
//				System.out.println();
				
				valueHashMap.put(keyValue, rowEntry);
			}
			br.close();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}/*  end of getValuesFromFile method  */
	
	
	/*  start of calculateIFs method  */
	private void calculateIFs(HashMap<String, double[]> numeratorHashMap, HashMap<String, double[]> denominatorHashMap, HashMap<String, double[]> IFsHashMap){
		
		Iterator<String> keyIterator = numeratorHashMap.keySet().iterator();
		
		while(keyIterator.hasNext()){
			String currentKey = keyIterator.next();
//			System.out.println(currentKey);
			
			
			if(numeratorHashMap.containsKey(currentKey) && denominatorHashMap.containsKey(currentKey)){
				double[] numeratorValues = numeratorHashMap.get(currentKey);
				double[] denominatorValues = denominatorHashMap.get(currentKey);
				
				//  make sure that the two arrays are of the same size (should be ten (10))
				if(numeratorValues.length != denominatorValues.length){
					System.out.println("Error: the length of the numerator values and the denominator values arrays are not the same.\nSystem.exit(0)");
					System.exit(0);
				}
				
				ArrayList<Double> IFvalues = new ArrayList<Double>(0);
				
				//  skip the first two entries because they are the fragment indices and
				//  skip the third entry because it is the 15-bp length probe that is used as the background measure
				for(int i=3; i<numeratorValues.length; i++){
					if((numeratorValues[i] != 0) && (denominatorValues[i] != 0)){
						IFvalues.add(numeratorValues[i] / denominatorValues[i]);
					}
				}
				IFvalues.trimToSize();
				
				//  if there is at least one IF value for the current fragment pair, calculate the average IF and store it in the IFsHashMap
				if(IFvalues.size() > 0){
					//  initialize the array to store the values for the current entry
					//  fragment index 1    -    fragment index 2    -    IF    -    stdev    -    count
					double[] IFvalue = new double[5];

					//  store the fragment indices
					IFvalue[0] = numeratorValues[0];
					IFvalue[1] = numeratorValues[1];
					
					//  make sure that the fragment indices for the numerator and denominator are identical
					if((IFvalue[0] != denominatorValues[0]) || (IFvalue[1] != denominatorValues[1])){
						System.out.println("Error: the fragment indices for the numerator and denominator do not match.\nSystem.exit(0)");
						System.exit(0);
					}
					
					//  calculate the average IF
					for(Double currentEntry: IFvalues){
						IFvalue[2] += currentEntry;
					}
					IFvalue[2] /= (double)IFvalues.size();
					
					//  calculate the standard deviation
					double stdev = 0;
					if(IFvalues.size() > 1){
						for(Double currentEntry: IFvalues){
							double term = currentEntry - IFvalue[2];
							term *= term;
							stdev += term;
						}
						stdev /= (double)((double)IFvalues.size() - 1.0);
						stdev = Math.sqrt(stdev);
					}
					IFvalue[3] = stdev;
					
					//  store the count
					IFvalue[4] = IFvalues.size();
					
					//  store the IF data for the current fragment pair
					IFsHashMap.put(currentKey, IFvalue);
//					System.out.println(currentKey + "\t" + (int)IFsHashMap.get(currentKey)[0] + "-" + (int)IFsHashMap.get(currentKey)[1] + "\t" + IFsHashMap.get(currentKey)[2]
//							+ "\t" + IFsHashMap.get(currentKey)[3] + "\t" + (int)IFsHashMap.get(currentKey)[4]);
				}
			}
		}
		
		
	}/*  end of calculateIFs method  */
	
	
	/*  start of calculateNormalizationFactor method  */
	private double calculateNormalizationFactor(HashMap<String, double[]> numeratorIFsHashMap, HashMap<String, double[]> denominatorIFsHashMaps){
		
		HashMap<String, ArrayList<Double>> logRatiosHashMap = new HashMap<String, ArrayList<Double>>();
		
		Iterator<String> keyIterator = numeratorIFsHashMap.keySet().iterator();
		
		while(keyIterator.hasNext()){
			String currentKey = keyIterator.next();
//			System.out.println(currentKey);
			
			
			if(numeratorIFsHashMap.containsKey(currentKey) && denominatorIFsHashMaps.containsKey(currentKey)){
				double[] numeratorEntry = numeratorIFsHashMap.get(currentKey);
				double[] denominatorEntry = denominatorIFsHashMaps.get(currentKey);
				
				
				//  make sure that the two arrays are of the same size (should be ten (10))
				if(numeratorEntry.length != denominatorEntry.length){
					System.out.println("Error: the length of the numerator values and the denominator values arrays are not the same.\nSystem.exit(0)");
					System.exit(0);
				}
				
				
				//  make sure that the fragment indices match for the current entries
				if((numeratorEntry[0] != denominatorEntry[0]) || (numeratorEntry[1] != denominatorEntry[1])){
					System.out.println("Error: the fragment indices do not match.\nSystem.exit(0)");
					System.exit(0);
				}
				
				
				//  create the keys for each of the fragment indices
				String keyFragment1 = String.valueOf((int)numeratorEntry[0]);
				String keyFragment2 = String.valueOf((int)numeratorEntry[1]);
				
				//  calculate the log ratio for the current entries
				double log10Ratio = Math.log10(numeratorEntry[2] / denominatorEntry[2]);
//				System.out.println(currentKey + "\t" + numeratorEntry[2] + "\t" + denominatorEntry[2] + "\t" + log10Ratio);
				
				//  store the log ratio in the lists corresponding to the numerator and denominator fragment indices
				//  for the first fragment
				if(logRatiosHashMap.containsKey(keyFragment1)){
					ArrayList<Double> updatedCurrentList = logRatiosHashMap.get(keyFragment1);
					updatedCurrentList.add(log10Ratio);
					updatedCurrentList.trimToSize();
					logRatiosHashMap.put(keyFragment1, updatedCurrentList);
				}
				else{
					ArrayList<Double> newCurrentList = new ArrayList<Double>(1);
					newCurrentList.add(log10Ratio);
					newCurrentList.trimToSize();
					logRatiosHashMap.put(keyFragment1, newCurrentList);
				}
				//  for the second fragment
				if(logRatiosHashMap.containsKey(keyFragment2)){
					ArrayList<Double> updatedCurrentList = logRatiosHashMap.get(keyFragment2);
					updatedCurrentList.add(log10Ratio);
					updatedCurrentList.trimToSize();
					logRatiosHashMap.put(keyFragment2, updatedCurrentList);
				}
				else{
					ArrayList<Double> newCurrentList = new ArrayList<Double>(1);
					newCurrentList.add(log10Ratio);
					newCurrentList.trimToSize();
					logRatiosHashMap.put(keyFragment2, newCurrentList);
				}
			}
		}
		
		
		//  calculate the average log ratio for each fragment (entry in the logRatiosHashMap
		HashMap<String, Double> averageLogRatios = new HashMap<String, Double>(logRatiosHashMap.size());
		
		keyIterator = logRatiosHashMap.keySet().iterator();
		
		while(keyIterator.hasNext()){
			String currentKey = keyIterator.next();
//			System.out.println(currentKey);
		
			ArrayList<Double> currentEntry = logRatiosHashMap.get(currentKey);
			
			double averageLogRatio = 0;
			
			for(Double currentValue: currentEntry){
				averageLogRatio += currentValue;
			}
			if(currentEntry.size() > 1){
				averageLogRatio /= (double)currentEntry.size();
			}
			
			averageLogRatios.put(currentKey, averageLogRatio);
//			System.out.println(currentKey + "\t" + averageLogRatios.get(currentKey));
		}
		
		
		//  calculate the average of the inverse log ratio (this is the normalization factor)
		double normalizationFactorValue = 0.0;
		
		keyIterator = averageLogRatios.keySet().iterator();
		
		while(keyIterator.hasNext()){
			String currentKey = keyIterator.next();
//			System.out.println(currentKey);
		
			double currentAverageLogRatioEntry = averageLogRatios.get(currentKey);
			
			double currentInverseOfAverageLogRatioEntry = Math.pow(10, currentAverageLogRatioEntry);
			
			normalizationFactorValue += currentInverseOfAverageLogRatioEntry;
		}
		if(averageLogRatios.size() > 1){
			normalizationFactorValue /= (double)averageLogRatios.size();
		}
//		System.out.println("normalizationFactorValue = " + normalizationFactorValue);
		
		return normalizationFactorValue;
	}/*  end of calculateNormalizationFactor method  */
	
	
	/*  start of outputNormalizationFactor method  */
	private void outputNormalizationFactor(String outputFilename, String filenameConditionOne, String filenameConditionTwo, String filenameBackground, double normalizationFactor){
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));
			

			bw.write("In order to normalize the IF data for the condition corresponding to the file \"" + filenameConditionTwo + "\" to the condition corresponding to the file \"" +
					filenameConditionOne + "\",\nthe IF values for the condition corresponding to the file \"" + filenameConditionTwo + "\" should be multiplied by " + 
					formatter.format(normalizationFactor));
			bw.newLine();
			bw.flush();
			
			bw.newLine();
			bw.flush();
			
			bw.write("Input file for Condition One (the reference condition): \"" + filenameConditionOne + "\"");
			bw.newLine();
			bw.write("Input file for Condition Two (the condition being normalized): \"" + filenameConditionTwo + "\"");
			bw.newLine();
			bw.write("Input file for the background (used to calculate the IFs for both conditions): \"" + filenameBackground + "\"");
			bw.newLine();
			bw.newLine();
			bw.write("Normalization factor =\t" + normalizationFactor);
			bw.flush();
			
			bw.close();
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		System.out.println("Output file: " + outputFilename);
	}/*  end of outputNormalizationFactor method  */
	
	
	/*  start of runAndReturn method  */
	public double runAndReturn(String[] args){
		run(args);
		return normalizationFactor;
	}/*  end of runAndReturn method  */
	
	
	/*  start of run method  */
	public void run(String[] args){
		
		//  initialize the Strings for the filenames
		String filenameConditionOne = "default_conditionOne.txt";
		String filenameConditionTwo = "default_conditionTwo.txt";
		String filenameBackground = "default_background.txt";
		
		if(args.length == 3){
			filenameConditionOne = args[0];
			filenameConditionTwo = args[1];
			filenameBackground = args[2];
		}
		else if(args.length == 5){
			filenameConditionOne = args[0];
			filenameConditionTwo = args[1];
			filenameBackground = args[2];
			inputPath = args[3];
			outputPath = args[4];
		}
		else{
			System.out.println("Error: please use the following pattern (without the quotes)\n" +
					"\t\"java NormalizationGeneDesert conditionOneFilename.txt conditionTwoFilename.txt backgroundFilename.txt\"\n" +
					"System.exit(0)");
			System.exit(0);
		}
		
		
		//  initialize the hashmaps that will store the raw intensity data
		HashMap<String, double[]> conditionOneValues = new HashMap<String, double[]>();
		HashMap<String, double[]> conditionTwoValues = new HashMap<String, double[]>();
		HashMap<String, double[]> backgroundValues = new HashMap<String, double[]>();
		
		//  read the raw intensity data into the hashmaps
		getValuesFromFile(filenameConditionOne, conditionOneValues);
		getValuesFromFile(filenameConditionTwo, conditionTwoValues);
		getValuesFromFile(filenameBackground, backgroundValues);
		
//		System.out.println("conditionOneValues size = " + conditionOneValues.size());
//		System.out.println("conditionTwoValues size = " + conditionTwoValues.size());
//		System.out.println("backgroundValues size = " + backgroundValues.size());
		
		
		//  initialize the hashmaps that will store the IF values
		HashMap<String, double[]> conditionOneIFs = new HashMap<String, double[]>();
		HashMap<String, double[]> conditionTwoIFs = new HashMap<String, double[]>();
		
		
		//  calculate the IFs for each of the conditions
		calculateIFs(conditionOneValues, backgroundValues, conditionOneIFs);
		calculateIFs(conditionTwoValues, backgroundValues, conditionTwoIFs);
		
//		System.out.println("conditionOneIFs size = " + conditionOneIFs.size());
//		System.out.println("conditionTwoIFs size = " + conditionTwoIFs.size());
		
		
		//  calculate the normalization factor
		normalizationFactor = calculateNormalizationFactor(conditionOneIFs, conditionTwoIFs);
		
		
		//  output the normalization factor to file
		outputNormalizationFactor("normalizationFactor.txt", filenameConditionOne, filenameConditionTwo, filenameBackground, normalizationFactor);
		
		
//		System.out.println("The normalization factor = " + normalizationFactor);
		System.out.println("\nIn order to normalize the IF data for the condition corresponding to the file \"" + filenameConditionTwo + "\" to the condition corresponding to the file \"" +
				filenameConditionOne + "\",\nthe IF values for the condition corresponding to the file \"" + filenameConditionTwo + "\" should be multiplied by " +
				formatter.format(normalizationFactor));
	}/*  end of run method  */
	
	
}/*  end of NormalizationGeneDesert class  */
